package org.ramoths.netcom.sessions;

import org.ramoths.netcom.messagesClients.Message;
import org.ramoths.netcom.messagesClients.MessageInputStream;
import org.ramoths.netcom.messagesClients.MessageObjet;
import org.ramoths.netcom.messagesClients.MessageOutputStream;
import org.ramoths.netcom.messagesSession.MsgGet;
import org.ramoths.netcom.messagesSession.MsgInputStream;
import org.ramoths.netcom.messagesSession.MsgInputStreamFermer;
import org.ramoths.netcom.messagesSession.MsgInputStreamFin;
import org.ramoths.netcom.messagesSession.MsgInputStreamFinException;
import org.ramoths.netcom.messagesSession.MsgInputStreamGetPortion;
import org.ramoths.netcom.messagesSession.MsgInputStreamPortion;
import org.ramoths.netcom.messagesSession.MsgObjet;
import org.ramoths.netcom.messagesSession.MsgOutputStream;
import org.ramoths.netcom.messagesSession.MsgOutputStreamFermer;
import org.ramoths.netcom.messagesSession.MsgOutputStreamFin;
import org.ramoths.netcom.messagesSession.MsgOutputStreamFinException;
import org.ramoths.netcom.messagesSession.MsgOutputStreamPortion;
import org.ramoths.netcom.messagesSession.MsgOutputStreamPortionIntegree;
import org.ramoths.netcom.outils.Outils;
import org.ramoths.netcom.sessions.exceptions.CommunicationException;
import org.ramoths.netcom.sessions.exceptions.MessageInvalideException;
import org.ramoths.netcom.sessions.exceptions.SerialisationException;
import org.ramoths.netcom.sessions.verrous.VrEnvoiMessage;
import org.ramoths.netcom.sessions.verrous.VrReceptionMessage;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class SessionCommunication
{
	private final SessionObjet sessionObjet;
	private final ThSessionInterpreteur thSessionInterpreteur;
	//
	private int nombreOrdre;
	private final VrEnvoiMessage vrEnvoiMessage;
	//
	private final List<Message> messagesRecus;
	private final VrReceptionMessage vrReceptionMessage;
	private int nombreDemandesAnnulees;
	private final Object mutexNombreDemandesAnnulees;
	//
	private final Map<Integer, ThInputStreamEnvoye> inputStreamsEnvoyes;
	private final Map<Integer, ThOutputStreamEnvoye> outputStreamsEnvoyes;
	//
	private final Map<Integer, InputStreamConsommateur> inputStreamsRecus;
	private final Map<Integer, OutputStreamConsommateur> outputStreamsRecus;
	//
	private final int tailleTemponInputStream;
	private final float tauxMinimumInputStream;
	private final int tailleTemponOutputStream;
	private final float tauxMinimumOutputStream;
	
	public SessionCommunication(SessionObjet sessionObjet, int tailleTemponInputStream, float tauxMinimumInputStream, int tailleTemponOutputStream, float tauxMinimumOutputStream)
	{
		this.sessionObjet = sessionObjet;
		//
		this.nombreOrdre = 0;
		this.vrEnvoiMessage = new VrEnvoiMessage();
		//
		this.messagesRecus = new ArrayList<Message>();
		this.vrReceptionMessage = new VrReceptionMessage();
		this.nombreDemandesAnnulees = 0;
		this.mutexNombreDemandesAnnulees = new Object();
		//
		this.inputStreamsEnvoyes = new TreeMap<Integer, ThInputStreamEnvoye>();
		this.outputStreamsEnvoyes = new TreeMap<Integer, ThOutputStreamEnvoye>();
		//
		this.inputStreamsRecus = new TreeMap<Integer, InputStreamConsommateur>();
		this.outputStreamsRecus = new TreeMap<Integer, OutputStreamConsommateur>();
		//
		this.tailleTemponInputStream = tailleTemponInputStream;
		this.tauxMinimumInputStream = tauxMinimumInputStream;
		this.tailleTemponOutputStream = tailleTemponOutputStream;
		this.tauxMinimumOutputStream = tauxMinimumOutputStream;
		//
		this.thSessionInterpreteur = new ThSessionInterpreteur(this);
		this.thSessionInterpreteur.start();
	}
	
	void runInterpreteur()
	{
		Object msg = null;
		while (true)
		{
			try
			{
				msg = sessionObjet.recevoir();
			}
			catch (CommunicationException ex)
			{
				// TODO
				break;
			}
			//
			if (msg instanceof MsgGet)
			{
				vrEnvoiMessage.lock();
				try
				{
					nombreOrdre += 1;
					vrEnvoiMessage.getConditionAutorisationEnvoi().signal();
				}
				finally
				{
					vrEnvoiMessage.unlock();
				}
			}
			else if (msg instanceof MsgObjet)
			{
				MessageObjet message = new MessageObjet(((MsgObjet) msg).getObjet());
				vrReceptionMessage.lock();
				try
				{
					messagesRecus.add(message);
					vrReceptionMessage.getConditionMessageRecu().signal();
				}
				finally
				{
					vrReceptionMessage.unlock();
				}
			}
			else if (msg instanceof MsgInputStream)
			{
				InputStreamConsommateur inputStream = new InputStreamConsommateur(
						this,
						((MsgInputStream) msg).getId(),
						tailleTemponInputStream,
						tauxMinimumInputStream);
				synchronized (inputStreamsRecus)
				{
					if (inputStreamsRecus.put(inputStream.getIdInputStream(), inputStream) != null)
					{
						// TODO
					}
				}
				MessageInputStream message = new MessageInputStream(inputStream);
				//
				vrReceptionMessage.lock();
				try
				{
					messagesRecus.add(message);
					vrReceptionMessage.getConditionMessageRecu().signal();
				}
				finally
				{
					vrReceptionMessage.unlock();
				}
			}
			else if (msg instanceof MsgInputStreamGetPortion)
			{
				ThInputStreamEnvoye th = null;
				synchronized (inputStreamsEnvoyes)
				{
					th = inputStreamsEnvoyes.get(((MsgInputStreamGetPortion) msg).getId());
				}
				if (th == null)
				{
					// TODO
				}
				th.lire(((MsgInputStreamGetPortion) msg).getTaille());
			}
			else if (msg instanceof MsgInputStreamFermer)
			{
				ThInputStreamEnvoye th = null;
				synchronized (inputStreamsEnvoyes)
				{
					th = inputStreamsEnvoyes.get(((MsgInputStreamGetPortion) msg).getId());
				}
				if (th == null)
				{
					// TODO
				}
				th.lire(-1);
			}
			else if (msg instanceof MsgInputStreamPortion)
			{
				InputStreamConsommateur inputStream = null;
				synchronized (inputStreamsRecus)
				{
					inputStream = inputStreamsRecus.get(((MsgInputStreamPortion) msg).getId());
				}
				if (inputStream == null)
				{
					// TODO
				}
				inputStream.setDonnees(
						((MsgInputStreamPortion) msg).getDonnees(),
						false,
						null);
			}
			else if (msg instanceof MsgInputStreamFin)
			{
				InputStreamConsommateur inputStream = null;
				synchronized (inputStreamsRecus)
				{
					inputStream = inputStreamsRecus.remove(((MsgInputStreamFin) msg).getId());
				}
				if (inputStream == null)
				{
					// TODO
				}
				inputStream.setDonnees(
						null,
						true,
						null);
			}
			else if (msg instanceof MsgInputStreamFinException)
			{
				InputStreamConsommateur inputStream = null;
				synchronized (inputStreamsRecus)
				{
					inputStream = inputStreamsRecus.remove(((MsgInputStreamFinException) msg).getId());
				}
				if (inputStream == null)
				{
					// TODO
				}
				inputStream.setDonnees(
						null,
						false,
						((MsgInputStreamFinException) msg).getException());
			}
			else if (msg instanceof MsgOutputStream)
			{
				OutputStreamConsommateur outputStream = new OutputStreamConsommateur(
						this,
						((MsgOutputStream) msg).getId(),
						tailleTemponOutputStream,
						tauxMinimumOutputStream);
				synchronized (outputStreamsRecus)
				{
					if (outputStreamsRecus.put(outputStream.getIdOutputStream(), outputStream) != null)
					{
						// TODO
					}
				}
				MessageOutputStream message = new MessageOutputStream(outputStream);
				vrReceptionMessage.lock();
				try
				{
					messagesRecus.add(message);
					vrReceptionMessage.getConditionMessageRecu().signal();
				}
				finally
				{
					vrReceptionMessage.unlock();
				}
			}
			else if (msg instanceof MsgOutputStreamPortion)
			{
				ThOutputStreamEnvoye th = null;
				synchronized (outputStreamsEnvoyes)
				{
					th = outputStreamsEnvoyes.get(((MsgOutputStreamPortion) msg).getId());
				}
				if (th == null)
				{
					// TODO
				}
				th.ecrire(((MsgOutputStreamPortion) msg).getDonnees());
			}
			else if (msg instanceof MsgOutputStreamPortionIntegree)
			{
				OutputStreamConsommateur outputStream = null;
				synchronized (outputStreamsRecus)
				{
					outputStream = outputStreamsRecus.get(((MsgOutputStreamPortionIntegree) msg).getId());
				}
				if (outputStream == null)
				{
					// TODO
				}
				outputStream.setDonnees(null);
			}
			else if (msg instanceof MsgOutputStreamFin)
			{
				OutputStreamConsommateur outputStream = null;
				synchronized (outputStreamsRecus)
				{
					outputStream = outputStreamsRecus.remove(((MsgOutputStreamFin) msg).getId());
				}
				if (outputStream == null)
				{
					// TODO
				}
				outputStream.setDonnees(null);
			}
			else if (msg instanceof MsgOutputStreamFinException)
			{
				OutputStreamConsommateur outputStream = null;
				synchronized (outputStreamsRecus)
				{
					outputStream = outputStreamsRecus.remove(((MsgOutputStreamFinException) msg).getId());
				}
				if (outputStream == null)
				{
					// TODO
				}
				outputStream.setDonnees(((MsgOutputStreamFinException) msg).getException());
			}
			else if (msg instanceof MsgOutputStreamFermer)
			{
				ThOutputStreamEnvoye th = null;
				synchronized (outputStreamsEnvoyes)
				{
					th = outputStreamsEnvoyes.get(((MsgOutputStreamPortion) msg).getId());
				}
				if (th == null)
				{
					// TODO
				}
				th.ecrire(null);
			}
			else
			{
				// TODO
			}
		}
	}
	
	void iscDemanderPortion(int idInputStream, int tailleADemander)
	{
		MsgInputStreamGetPortion msg = new MsgInputStreamGetPortion(idInputStream, tailleADemander);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	void iscEnvoyerFermeture(int idInputStream)
	{
		MsgInputStreamFermer msg = new MsgInputStreamFermer(idInputStream);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	void iseEnvoyerFinException(int idInputStream, Exception exception)
	{
		MsgInputStreamFinException msg = new MsgInputStreamFinException(idInputStream, exception);
		while (true)
		{
			try
			{
				sessionObjet.envoyer(msg);
			}
			catch (CommunicationException ex)
			{
				// TODO
			}
			catch (SerialisationException ex)
			{
				msg.setException(new Exception("Exception non sérialisable de lecture de flux!"));
				continue;
			}
			break;
		}
	}
	
	void iseEnvoyerFin(int idInputStream)
	{
		MsgInputStreamFin msg = new MsgInputStreamFin(idInputStream);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	void iseFinInputStream(ThInputStreamEnvoye inputStream)
	{
		synchronized (inputStreamsEnvoyes)
		{
			inputStreamsEnvoyes.remove(inputStream.getIdInputStream());
		}
	}
	
	void iseDonnees(int idInputStream, byte[] donnees, int tailleDonnees)
	{
		byte[] tmp = donnees;
		if (donnees.length != tailleDonnees)
		{
			tmp = new byte[tailleDonnees];
			System.arraycopy(donnees, 0, tmp, 0, tailleDonnees);
		}
		MsgInputStreamPortion msg = new MsgInputStreamPortion(idInputStream, tmp);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	void oseEnvoyerFinException(int idOutputStream, Exception exception)
	{
		MsgOutputStreamFinException msg = new MsgOutputStreamFinException(idOutputStream, exception);
		while (true)
		{
			try
			{
				sessionObjet.envoyer(msg);
			}
			catch (CommunicationException ex)
			{
				// TODO
			}
			catch (SerialisationException ex)
			{
				msg.setException(new Exception("Exception non sérialisable de fermeture de flux!"));
				continue;
			}
			break;
		}
	}
	
	void oseEnvoyerFin(int idOutputStream)
	{
		MsgOutputStreamFin msg = new MsgOutputStreamFin(idOutputStream);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}

	void oseEnvoyerNotifPortionIntegree(int idOutputStream)
	{
		MsgOutputStreamPortionIntegree msg = new MsgOutputStreamPortionIntegree(idOutputStream);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	void oseFinOutputStream(ThOutputStreamEnvoye outputStream)
	{
		synchronized (outputStreamsEnvoyes)
		{
			outputStreamsEnvoyes.remove(outputStream.getIdOutputStream());
		}
	}
	
	void oscEnvoyerDonneesOutputStream(int idOutputStream, byte[] donnees)
	{
		MsgOutputStreamPortion msg = new MsgOutputStreamPortion(idOutputStream, donnees);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	void oscEnvoyerFermeture(int idOutputStream)
	{
		MsgOutputStreamFermer msg = new MsgOutputStreamFermer(idOutputStream);
		try
		{
			sessionObjet.envoyer(msg);
		}
		catch (CommunicationException ex)
		{
			// TODO
		}
		catch (SerialisationException ex)
		{
			/* Impossible */
		}
	}
	
	public SuiviFlux envoyer(Message message) throws InterruptedException, SerialisationException, CommunicationException, MessageInvalideException
	{
		vrEnvoiMessage.lock();
		try
		{
			while (nombreOrdre == 0)
				vrEnvoiMessage.getConditionAutorisationEnvoi().await();
			nombreOrdre -= 1;
		}
		finally
		{
			vrEnvoiMessage.unlock();
		}
		//
		SuiviFlux suivi = new SuiviFlux();
		//
		if (message instanceof MessageObjet)
		{
			MsgObjet msg = new MsgObjet(((MessageObjet) message).getObjet());
			try
			{
				sessionObjet.envoyer(msg);
			}
			catch (CommunicationException ex)
			{
				// TODO
				throw ex;
			}
			suivi.setFinFlux(null);
		}
		else if (message instanceof MessageInputStream)
		{
			MsgInputStream msg = new MsgInputStream();
			synchronized (inputStreamsEnvoyes)
			{
				Integer id = null;
				while (id == null || inputStreamsEnvoyes.containsKey(id))
					id = Integer.valueOf(Outils.getSRand().nextInt());
				ThInputStreamEnvoye th = new ThInputStreamEnvoye(this, id.intValue(), ((MessageInputStream) message).getInputStream(), suivi);
				th.start();
				inputStreamsEnvoyes.put(id, th);
				msg.setId(id);
			}
			try
			{
				sessionObjet.envoyer(msg);
			}
			catch (CommunicationException ex)
			{
				// TODO
				throw ex;
			}
			catch (SerialisationException ex)
			{
				/* Impossible */
			}
		}
		else if (message instanceof MessageOutputStream)
		{
			MsgOutputStream msg = new MsgOutputStream();
			synchronized (outputStreamsEnvoyes)
			{
				Integer id = null;
				while (id == null || outputStreamsEnvoyes.containsKey(id))
					id = Integer.valueOf(Outils.getSRand().nextInt());
				ThOutputStreamEnvoye th = new ThOutputStreamEnvoye(this, id.intValue(), ((MessageOutputStream) message).getOutputStream(), suivi);
				th.start();
				outputStreamsEnvoyes.put(id, th);
				msg.setId(id);
			}
			try
			{
				sessionObjet.envoyer(msg);
			}
			catch (CommunicationException ex)
			{
				// TODO
				throw ex;
			}
			catch (SerialisationException ex)
			{
				/* Impossible */
			}
		}
		else
			throw MessageInvalideException.INSTANCE;
		//
		return suivi;
	}
	
	public Message recevoir() throws CommunicationException, InterruptedException
	{
		synchronized (mutexNombreDemandesAnnulees)
		{
			if (nombreDemandesAnnulees == 0)
			{
				try
				{
					sessionObjet.envoyer(MsgGet.INSTANCE);
				}
				catch (CommunicationException ex)
				{
					// TODO
					throw ex;
				}
				catch (SerialisationException ex)
				{
					/* Impossible */
				}
			}
			else
				nombreDemandesAnnulees -= 1;
		}
		//
		Message message = null;
		vrReceptionMessage.lock();
		try
		{
			try
			{
				while (messagesRecus.isEmpty())
					vrReceptionMessage.getConditionMessageRecu().await();
			}
			catch (InterruptedException ex)
			{
				synchronized (mutexNombreDemandesAnnulees)
				{
					nombreDemandesAnnulees += 1;
				}
				throw ex;
			}
			message = messagesRecus.remove(0);
		}
		finally
		{
			vrReceptionMessage.unlock();
		}
		//
		return message;
	}
	
	public void fermer()
	{
		sessionObjet.fermer();
	}
}
